package logrus_util

import (
	"bytes"
	"crypto/tls"
	"encoding/json"
	"fmt"
	"github.com/sirupsen/logrus"
	"net"
	"net/mail"
	"net/smtp"
	"strings"
)

const (
	DefaultMailFormat = "Subject: %s\r\n\r\nMessage: \r\n\r\n%s\r\n\r\nFields:\r\n\r\n%s"
)

type IMailConfig interface {
	GetAppName() string
	GetHost() string
	GetPort() int
	GetSSL() bool
	GetAuth() bool
	GetUsername() string
	GetPassword() string
	GetFrom() string
	GetTo() string
	GetLevels() []logrus.Level
}

type MailConfig struct {
	AppName  string
	Host     string
	Port     int
	SSL      bool
	Auth     bool
	Username string
	Password string
	From     string
	To       string
	Levels   []logrus.Level
}

func (c MailConfig) GetAppName() string {
	return c.AppName
}

func (c MailConfig) GetHost() string {
	return c.Host
}

func (c MailConfig) GetPort() int {
	return c.Port
}

func (c MailConfig) GetSSL() bool {
	return c.SSL
}

func (c MailConfig) GetAuth() bool {
	return c.Auth
}

func (c MailConfig) GetUsername() string {
	return c.Username
}

func (c MailConfig) GetPassword() string {
	return c.Password
}

func (c MailConfig) GetFrom() string {
	return c.From
}

func (c MailConfig) GetTo() string {
	return c.To
}

func (c MailConfig) GetLevels() []logrus.Level {
	return c.Levels
}

type MailHook struct {
	AppName  string
	Host     string
	Port     int
	SSL      bool
	Auth     bool
	Username string
	Password string
	From     *mail.Address
	To       *mail.Address
	Lvs      []logrus.Level
}

func NewMailHook(conf IMailConfig) (hook *MailHook, err error) {
	sender, err := mail.ParseAddress(conf.GetFrom())
	if err != nil {
		return nil, fmt.Errorf("parse sender address error: %s", err.Error())
	}

	recipient, err := mail.ParseAddress(conf.GetTo())
	if err != nil {
		return nil, fmt.Errorf("parse recipient address error: %s", err.Error())
	}

	var levels = []logrus.Level{
		logrus.PanicLevel,
		logrus.FatalLevel,
		logrus.ErrorLevel,
	}
	if lvs := conf.GetLevels(); nil != lvs {
		levels = lvs
	}

	hook = &MailHook{
		AppName:  conf.GetAppName(),
		Host:     conf.GetHost(),
		Port:     conf.GetPort(),
		SSL:      conf.GetSSL(),
		Auth:     conf.GetAuth(),
		Username: conf.GetUsername(),
		Password: conf.GetPassword(),
		From:     sender,
		To:       recipient,
		Lvs:      levels,
	}

	conn, err := hook.dial()
	if nil != err {
		return nil, fmt.Errorf("dial error: %s", err.Error())
	}
	_ = conn.Close()

	return
}

func (hook *MailHook) dial() (conn net.Conn, err error) {
	if hook.SSL {
		conn, err = tlsDial(hook.Host, hook.Port)
	} else {
		conn, err = net.Dial("tcp", fmt.Sprintf("%s:%v", hook.Host, hook.Port))
	}

	return
}

func (hook *MailHook) Fire(entry *logrus.Entry) error {
	conn, err := hook.dial()
	if nil != err {
		return fmt.Errorf("dial error: %s", err.Error())
	}

	c, err := smtp.NewClient(conn, hook.Host)
	if nil != err {
		return fmt.Errorf("new smtp client error: %s", err.Error())
	}

	if hook.Auth {
		auth := smtp.PlainAuth("", hook.Username, hook.Password, hook.Host)
		if err := c.Auth(auth); nil != err {
			return fmt.Errorf("client auth error: %s", err.Error())
		}
	}

	if err := c.Mail(hook.From.Address); nil != err {
		return fmt.Errorf("set client from error: %s", err.Error())
	}

	if err := c.Rcpt(hook.To.Address); nil != err {
		return fmt.Errorf("set client to error: %s", err.Error())
	}

	message := createMessage(entry, hook.AppName)

	writer, err := c.Data()
	if nil != err {
		return fmt.Errorf("get writer error: %s", err.Error())
	}

	if _, err := writer.Write(message.Bytes()); nil != err {
		return fmt.Errorf("write message error: %s", err.Error())
	}

	if err := writer.Close(); nil != err {
		return fmt.Errorf("close writer error: %s", err.Error())
	}

	if err := c.Quit(); nil != err {
		return fmt.Errorf("quit client error: %s", err.Error())
	}

	return nil
}

func (hook *MailHook) Levels() []logrus.Level {
	return hook.Lvs
}

func tlsDial(host string, port int) (*tls.Conn, error) {
	tlsConfig := &tls.Config{
		InsecureSkipVerify: true,
		ServerName:         host,
	}

	return tls.Dial("tcp", fmt.Sprintf("%s:%v", host, port), tlsConfig)
}

func createMessage(entry *logrus.Entry, appName string) *bytes.Buffer {
	body, _ := Formatter{}.Format(entry)
	title := fmt.Sprintf("[%s] %s", strings.ToUpper(entry.Level.String()), appName)
	fields, _ := json.MarshalIndent(entry.Data, "", "\t")
	contents := fmt.Sprintf(DefaultMailFormat, title, string(body), fields)
	message := bytes.NewBufferString(contents)
	return message
}
